﻿using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Loong.Threading.Locks
{
    public abstract class LockFactoryBase : ILockFactory
    {
        private static readonly AsyncLocal<Guid> _owner = new AsyncLocal<Guid>();

        private readonly Dictionary<string, HashSet<Guid>> _lockedResources;

        private readonly ILogger _logger;
        private readonly LockOptions _lockOptions;

        public LockFactoryBase(ILogger logger, IOptionsMonitor<LockOptions> options)
        {
            _lockedResources = new Dictionary<string, HashSet<Guid>>();

            _logger = logger;
            _lockOptions = options.CurrentValue;
        }

        public string GetOrCreateOwner()
        {
            var owner = _owner.Value;
            if (owner == Guid.Empty)
            {
                owner = Guid.NewGuid();
                _owner.Value = owner;
            }

            return owner.ToString();
        }

        public async Task<ILock> LockAsync(string resource, string owner)
        {
            return await LockAsync(resource, owner, _lockOptions.DefaultExpireTime);
        }

        public async Task<ILock> LockAsync(string resource, string owner, TimeSpan expiryTime)
        {
            var lockId = Guid.NewGuid();
            var key = BuildKey(resource, owner);

            if (!_lockedResources.ContainsKey(key))
            {
                try
                {
                    await AcquireAsync(resource, owner, expiryTime);

                    _lockedResources.Add(key, new HashSet<Guid>());
                }
                catch (Exception ex)
                {
                    _logger.LogError($"{resource}--{ex.Message}");

                    ReleaseLock(resource, owner, lockId);

                    throw;
                }
            }

            _lockedResources[key].Add(lockId);

            return new Lock(resource, owner, lockId, this);
        }

        protected abstract Task AcquireAsync(string resource, string owner, TimeSpan expiryTime);

        public void ReleaseLock(string resource, string owner, Guid lockId)
        {
            try
            {
                var key = BuildKey(resource, owner);

                if (_lockedResources.ContainsKey(key))
                {
                    if (_lockedResources[key].Contains(lockId))
                    {
                        _lockedResources[key].Remove(lockId);
                        if (_lockedResources[key].Count == 0)
                        {
                            _lockedResources.Remove(key);
                            Release(resource, owner);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message);
            }
        }

        protected abstract void Release(string resource, string owner);

        protected string BuildKey(string resource, string owner)
        {
            return $"{resource}:{owner}";
        }

        private bool _isDisposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!_isDisposed)
            {
                if (disposing)
                {
                    _lockedResources.Clear();
                }

                _isDisposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}
